package top.winshu.common.web.config;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.dao.DataAccessException;
import org.springframework.http.HttpStatus;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.util.StringUtils;
import org.springframework.validation.BindException;
import org.springframework.validation.BindingResult;
import org.springframework.validation.FieldError;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.client.HttpClientErrorException;
import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException;
import top.winshu.common.constant.ExceptionCode;
import top.winshu.common.exception.WinshuException;
import top.winshu.common.exception.WinshuInternalException;
import top.winshu.common.exception.WinshuServiceException;
import top.winshu.common.web.RestResponse;
import top.winshu.common.web.context.RequestContext;
import top.winshu.common.web.context.RequestContextUtil;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.sql.SQLException;

/**
 * 全局异常处理
 * 粒度需小心控制
 *
 * @author winshu
 * @since 2020/9/15
 **/
@SuppressWarnings("rawtypes")
@RestControllerAdvice
public class GlobalExceptionAdvice {

    private static final Logger LOGGER = LoggerFactory.getLogger(GlobalExceptionAdvice.class);

    /**
     * 默认异常处理
     */
    @ExceptionHandler(Exception.class)
    public RestResponse defaultException(Exception e) {
        logRequestParameter();
        LOGGER.error("系统异常", e);

        return RestResponse.fail("系统异常", e);
    }

    @ExceptionHandler({HttpMessageNotReadableException.class, MethodArgumentTypeMismatchException.class, HttpRequestMethodNotSupportedException.class})
    public RestResponse requestException(Exception e, HttpServletRequest request) {
        logRequestParameter();
        LOGGER.error("请求参数异常, {}", request.getServletPath(), e);

        return RestResponse.fail("请求参数异常", e);
    }

    @ExceptionHandler({BindException.class, MethodArgumentNotValidException.class})
    public RestResponse validException(BindException e) {
        logRequestParameter();
        LOGGER.warn("参数校验失败", e);

        BindingResult bindingResult = e.getBindingResult();
        StringBuilder builder = new StringBuilder();
        for (FieldError fieldError : bindingResult.getFieldErrors()) {
            builder.append(fieldError.getDefaultMessage()).append("\n");
        }
        return RestResponse.fail(ExceptionCode.PARAM, builder.toString(), getStack(e));
    }

    @ExceptionHandler({DataAccessException.class, SQLException.class})
    public RestResponse databaseException(Exception e) {
        logRequestParameter();
        LOGGER.error("数据库异常", e);

        return RestResponse.fail(ExceptionCode.DATABASE, "数据库异常", getStack(e));
    }

    /**
     * 业务异常，打印堆栈
     */
    @ExceptionHandler(WinshuException.class)
    public RestResponse winshuException(WinshuException e) {
        logRequestParameter();
        LOGGER.error("业务异常", e);

        String message = StringUtils.hasLength(e.getMessage()) ? e.getMessage() : "业务异常";
        return RestResponse.fail(ExceptionCode.BUSINESS, message, getStack(e));
    }

    /**
     * 服务异常，不打印堆栈
     */
    @ExceptionHandler(WinshuServiceException.class)
    public RestResponse winshuServiceException(WinshuServiceException e) {
        LOGGER.error("服务异常: {}", e.getMessage());
        String message = StringUtils.hasLength(e.getMessage()) ? e.getMessage() : "服务异常";
        return RestResponse.fail(ExceptionCode.SERVICE, message, getStack(e));
    }

    /**
     * http异常
     */
    @ExceptionHandler(HttpClientErrorException.class)
    public void httpException(HttpClientErrorException e, HttpServletResponse response) {
        try {
            response.setStatus(e.getStatusCode().value());
            response.getWriter().write(e.getStatusText());
        } catch (IOException ioException) {
            LOGGER.warn("打印日志错误: {}", ioException.getMessage(), ioException);
        }
    }

    /**
     * 特殊处理
     */
    @ExceptionHandler(WinshuInternalException.class)
    public void winshuInternalException(WinshuInternalException e, HttpServletResponse response) {
        try {
            response.setStatus(HttpStatus.INTERNAL_SERVER_ERROR.value());
            response.getWriter().write(e.getMessage());
        } catch (IOException ioException) {
            LOGGER.warn("打印日志错误: {}", ioException.getMessage(), ioException);
        }
    }

    protected static void logRequestParameter() {
        RequestContext context = RequestContextUtil.getRequestContext();
        if (context == null) {
            return;
        }
        LOGGER.error("\n请求方法: {}\n请求参数: [{}]", context.getSignature(), WebLogAspect.getJsonArgs());
    }

    protected static String getStack(Throwable e) {
        return e.getClass().getName() + "/" + (e.getMessage() == null ? "" : e.getMessage());
    }
}
